package main

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"sync"
)

/**
 * @Description:支持断点下载
原理:请求附件地址,通过获取附件的content-length 的长度,获取文件大小,然后对比要下载的附件是否存在,不存在则新建,存在则看一下当前附件下载的进度,
如果文件部分下载,则通过请求设置range的请求参数,获取剩余附件的内容,然后写入到本地文件
*/
// 它尝试从指定的URL下载文件并保存到本地文件系统。
func main() {
	url := "http://*********/static/vedio/%E5%8C%97%E4%BA%AC%E5%B8%82%E7%87%83%E6%B0%94%E4%BE%9B%E5%BA%94%E5%8F%8A%E6%B4%BE%E5%8D%95%E6%80%BB%E4%BD%93%E6%83%85%E5%86%B5-%E5%B8%82%E5%9F%8E%E7%AE%A1%E5%A7%94%E6%8E%A5%E8%AF%89%E5%8D%B3%E5%8A%9E%E5%9F%B9%E8%AE%AD%E7%AC%AC%E4%B8%80%E8%AE%B2.mp4"
	filePath := "333.mp4"
	// 调用downloadFile函数开始下载过程
	err := downloadFile(url, filePath)
	if err != nil {
		fmt.Println("下载失败:", err)
	} else {
		fmt.Println("下载成功！")
	}
}

// downloadFile 从指定的URL下载文件并保存到本地文件系统。
// 支持断点续传，如果文件已部分下载，则从中断处继续。
// 参数：
//
//	url: 要下载的文件的URL。
//	filePath: 下载文件将被保存到的本地文件路径。
//
// 返回值：
//
//	error: 如果下载失败，返回错误信息。
func downloadFile(url string, filePath string) error {
	// 打开文件用于写入，如果不存在则创建。
	file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	// 发送HEAD请求到服务器以获取文件大小。
	resp, err := http.Head(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	// 将内容长度转换为整数以获取文件大小。
	fileSize, err := strconv.Atoi(resp.Header.Get("Content-Length"))
	if err != nil {
		return err
	}
	/**
	先定义好开多少个携程比如10个,goroutineNum = 10
	获取剩余附件的大小,计算每个协程需要下载的大小,通过大小在计算出每次range的start和end
	*/
	var wg sync.WaitGroup
	goroutineNum := 5
	everySize := fileSize / goroutineNum
	for i := 1; i <= goroutineNum; i++ {
		start := (i - 1) * everySize
		end := start + everySize
		if i == goroutineNum {
			end = fileSize
		}
		fmt.Printf("第%d个协程下载,start:%d,end:%d\n", i, start, end)
		wg.Add(1)
		go rangeDownLoad(url, int64(start), int64(end), i, &wg)
	}
	wg.Wait()
	// 将附件合并
	writer := bufio.NewWriter(file)
	defer writer.Flush()
	for i := 1; i <= goroutineNum; i++ {
		file2, err := os.Open(fmt.Sprintf("file%d.mp4", i))
		if err != nil {
			return err
		}
		defer file2.Close()
		_, err = io.Copy(writer, file2)
		if err != nil {
			return err
		}
	}
	return nil
}

/*
*
 */
func rangeDownLoad(url string, start, end int64, index int, wg *sync.WaitGroup) error {
	defer wg.Done()
	filePath := fmt.Sprintf("file%d.mp4", index)
	file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY, 0644)
	defer file.Close()
	// 创建一个GET请求，并设置Range头部以下载文件的剩余部分。
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return err
	}
	stat, err := os.Stat(filePath)
	if err == nil {
		partStart := stat.Size()
		_, err := file.Seek(partStart, 0)
		if err != nil {
			return err
		}
		start = start + partStart
	}
	req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end-1))

	// 使用HTTP客户端发送请求。
	client := &http.Client{}

	// 执行请求
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// 创建一个缓冲区来读取响应中的数据。
	buffer := make([]byte, 1024)

	// 分块读取响应数据并将其写入文件。
	for {
		n, err := resp.Body.Read(buffer)
		if n > 0 {
			_, err := file.Write(buffer[:n])
			if err != nil {
				return err
			}
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
	}
	return nil
}
